﻿using CrawlerApp.Core.Entities;
using CrawlerApp.Core.Entities.BookAggregate;
using CrawlerApp.Extensions;
using CrawlerApp.Infrastructure.Interfaces;
using CrawlerApp.Infrastructure.Services;
using CrawlerApp.Services.Interfaces;
using HtmlAgilityPack;
using Microsoft.Extensions.Configuration;
using Quartz;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace CrawlerApp.Services.Job
{
    public class BookJob : IJob
    {
        private readonly ILogger logger;
        private readonly IConfiguration configuration;
        private readonly IBookRepository repository;
        private readonly IRepository<Category> categoryRepository;
        private readonly IRepository<Author> authorRepository;
        private readonly IRepository<Tag> tagRepository;
        private readonly IHtmlPraseService htmlPrase;
        private const string url = "http://www.zxcs.me/map.html";

        public static event EventHandler SuccessHandler;
        public static event EventHandler FailedHandler;

        public BookJob(ILogger logger, 
            IConfiguration configuration, 
            IBookRepository repository, 
            IRepository<Category> categoryRepository, 
            IRepository<Author> authorRepository, 
            IRepository<Tag> tagRepository,
            IHtmlPraseService htmlPrase)
        {
            this.logger = logger.ForContext<BookJob>();
            this.configuration = configuration;
            this.repository = repository;
            this.categoryRepository = categoryRepository;
            this.authorRepository = authorRepository;
            this.tagRepository = tagRepository;
            this.htmlPrase = htmlPrase;
        }

        private async Task GetBookUrlList(Stack<string> urls, string urlTemplate, int page = 1)
        {
            var url = $"{urlTemplate}/{page}";
            page++;

            var result = await htmlPrase.GetSpecifyCollectionAsync(url, "//dl[@id='plist']/dt/a", selector => selector.Attributes["href"].Value);
            result.ToList().ForEach(url => urls.Push(url));

            page = await GetNextPageId(url, page);
            if (page != 0)
            {
                await GetBookUrlList(urls, urlTemplate, page);
            }
        }

        private async Task<int> GetNextPageId(string url, int page)
        {
            var result = await htmlPrase.GetSpecifyCollectionAsync(url, "//div[@id='pagenavi']/a", selector => selector.InnerText.ToInt());
            return result.SingleOrDefault(n => n == page);
        }

        private async Task CreateOrUpdateBookInfo(Stack<string> urls, IReadOnlyCollection<Category> dbCategorys, IReadOnlyCollection<Tag> dbTags)
        {
            if (urls.Count == 0) return;

            var url = urls.Pop();

            //var book = await htmlPrase.GetBookAsync(url);
            var book = new Book("name1", "href");

            try
            {
                if (!repository.Exists(b => b.Name == book.Name))
                {
                    //var author = await authorRepository.SingleOrDefaultAsync(a => a.Name == book.Author.Name);
                    //var category = await categoryRepository.SingleOrDefaultAsync(c => c.Name == book.Category.Name);

                    //if (author == null) author = await authorRepository.AddAsync(book.Author);
                    //if (category == null) category = await categoryRepository.AddAsync(book.Category);

                    book = await repository.AddAsync(book);
                }
            }
            catch(Exception ex)
            {
                Trace.WriteLine(ex);
            }
            await CreateOrUpdateBookInfo(urls, dbCategorys, dbTags);
        }

        private void GetBookByCategory(IReadOnlyCollection<Category> categories, IReadOnlyCollection<Tag> tags)
        {
            Parallel.ForEach(categories, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 5 }, (category) =>
             {
                 var document = new HtmlDocument();
                 var urlTemplate = $"{category.Href}/page";
                 Stack<string> urls = new Stack<string>();

                 GetBookUrlList(urls, urlTemplate).Wait();

                 if(urls.Count > 0)
                 {
                     CreateOrUpdateBookInfo(urls, categories, tags).Wait();
                 }
             });
        }

        private async Task<IReadOnlyCollection<Category>> CreateOrUpdateCategories()
        {
            var categories = await htmlPrase.GetSpecifyCollectionAsync(url, "//div[@id='sort']/ul/li/a", selector => new Category(selector.InnerText, selector.Attributes["href"].Value), it => !string.IsNullOrWhiteSpace(it.InnerText));

            var dbCategorys = await categoryRepository.ToListAsync(onChange: true);

            foreach (var category in categories)
            {
                if (dbCategorys.Any(c => c.Name == category.Name))
                {
                    //var newCategory = dbCategorys.SingleOrDefault(c => c.Name == category.Name && (c.Href != category.Href || c.Total != category.Total));
                    //if (newCategory != null)
                    //{
                    //    newCategory.SetHref(category.Href).SetLength(category.Total);
                    //    await categoryRepository.UpdateAsync(newCategory);
                    //}
                }
                else
                {
                    await categoryRepository.AddAsync(category);
                }
            }

            return await categoryRepository.ToListAsync();
        }

        private async Task<IReadOnlyCollection<Tag>> CreateOrUpdateTags()
        {
            var tags = await htmlPrase.GetSpecifyCollectionAsync(url, "//div[@id='tags']/ul/li/a", selector => new Tag(selector.InnerText, selector.Attributes["href"].Value));

            var dbTags = await tagRepository.ToListAsync(onChange: true);

            foreach (var tag in tags)
            {
                if (dbTags.Any(t => t.Name == tag.Name))
                {
                    //var newTag = dbTags.SingleOrDefault(t => t.Name == tag.Name && (t.Href != tag.Href || t.Length != tag.Length));
                    //if (newTag != null)
                    //{
                    //    newTag.SetHref(tag.Href).SetLength(tag.Length);
                    //    await tagRepository.UpdateAsync(newTag);
                    //}
                }
                else
                {
                    await tagRepository.AddAsync(tag);
                }
            }
            return await tagRepository.ToListAsync();
        }

        public async Task Execute(IJobExecutionContext context)
        {
            logger.Information("开始采集小说信息");

            try
            {
                var categories = await CreateOrUpdateCategories();

                var tags = await CreateOrUpdateTags();

                GetBookByCategory(categories, tags);

                SuccessHandler?.Invoke(this, null);
            }
            catch(Exception ex)
            {
                logger.Error(ex, ex.Message);
                FailedHandler?.Invoke(this, null);
            }

            logger.Information("小说采集信息结束");
        }
    }
}
